;   This program is free software: you can redistribute it and/or modify
;   it under the terms of the GNU General Public License as published by
;   the Free Software Foundation, either version 3 of the License, or
;   (at your option) any later version.
;
;   This program is distributed in the hope that it will be useful,
;   but WITHOUT ANY WARRANTY; without even the implied warranty of
;   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;   GNU General Public License for more details.
;
;   You should have received a copy of the GNU General Public License
;   along with this program.  If not, see <http://www.gnu.org/licenses/>.
;
;程序名称:2014cal.asm
;功能:1980-2099年历
;环境:16BIT DOS实模式(windows/dos或dosbox)
;编译器:MASM 5.1-6X 
;用法:看说明
;返回值:没有
;破坏寄存器:不适用
;
;原题目：http://tieba.baidu.com/p/3817224129
;实验设计:年历(2014)
;
;原题目只要求制作2014年历，但这不具通用性，现在这个是1980-2099的年历，
;每一次显示半年，按左右键查询其他年份，按ESC閃人。
;其实吧里已有TajuraTong姑娘曾写了一个，功能十分完整。
;http://tieba.baidu.com/p/3680768090
;只要稍稍修改就可以显示半年历了，我已征得TajuraTong姑娘的同意修改她的
;程式，在这里发布她的心得和释出她的代码/和本人小部份修改，
;这个程式包括想法和技术细节，都是她原创，本人纯粹是一只copycat。
;
;
;----------TajuraTong的理论----------------
;
;程式在纯dos下或者window 的 dosbox也可(最好用全萤幕) 。
;
;R,L 和　<- ->　均可左右查看月份，年份范围由(1980-2099)，这是int 21h的限制。按ESC可离开。
;int 21h里ah=2ah是读取日期，ah=2bh是设定日期，代码中没有建立每年／每月的资料结构，查看月份时才即时读取。
;
;想法：
;读日期时，windows用日期函数，dos的可以用int21h,2ah 或者系统中断int 1Ah
;看看int21h好了，int 21h里ah=2ah是读取日期，ah=2bh是设定日期
;
;先看看2A是做什么的：
;INT 21 - DOS 1+ - GET SYSTEM DATE
;
;AH = 2Ah
;Return: CX = year (1980-2099)
;DH = month
;DL = day
;---DOS 1.10+---
;AL = day of week (00h=Sunday)
;
;其中AL很重要，等于当天日期的星期数！我们只要把日期先 [设定]是某月的第１日，
;然后再读取该日的日期，那么就知道该月的第１日是星期一、二还是什么！
;
;接下来再看看ah=2Bh
;
;INT 21 - DOS 1+ - SET SYSTEM DATE
;AH = 2Bh
;CX = year (1980-2099)
;DH = month (1-12)
;DL = day (1-31)
;Return: AL = status
;00h successful
;FFh invalid date, system date unchanged
;
;注意这个AL = 状态，若是0表示成功，ff表示失败！
;我们知道，一年的月份中，最少是28日，最多是31日，只要由28日(dl=28)开始设定该月日期，
;成功则日期就dl +1，即29，再回去试，若再成功再dh+1回去试，试到32日，回存失败(al=ff)
;那最后的dl减1，就是该月的天数，这样就不用理会什么月大月小，二月有28或29日的麻烦问题了。
;到此，我们已经解决了两个关键问题，该月第一天的星期数，该月是28-31那一项？
;
;好了，设想完毕，开始设计表格!
;
;先排日期，我们把它排到惯见的方式：
;db ' 1 2 3 4 5 6 7 '
;db ' 8 9 10 11 12 13 14 '
;db '15 16 17 18 19 20 21 '
;db '22 23 24 25 26 27 28 '
;db '29 30 31 ' 
;
;每一个日期占3byte，[空白][word]，word＝2byte即日期，由1-31
;
;月份：
;db 'January ','February ','March ','April '
;db 'May ','June ','July ','August '
;db 'September ','October ','Novermber ','December '
;
;占14bytes，每个月份等长，不足的补以空白，这样，用指标x14，就是月份的字串位置了
;指标可以用左右键去加或减。
;
;再建立一个日历的字串缓冲，形式如下：
;
;年份 / 月份　　　　＜－可变 
;S M T W T F S 　＜－不变
;　 1 2 3 4 5 6 7
;8 9 10 11 12 13 14　　＜ －1到6列可变
;15 16 17 18 19 20 21
;22 23 24 25 26 27 28
;29 30 31 
;
;方法：
;0.先把原来日期存起来，int21, ah=2ah，得到年/月/日，存起来当作[年指标]和[月指标]的初始值
;1.利用设定和读取得出，算出该月的第一天星期数和该月的天数，把该年由hex转成文字存到缓冲字串的起点
;2.月份，利用月份指标x14得出该月字串存到缓冲区
;3.送星期字串， S M T W T F S　到缓冲区
;4.利用该月第１天的星期数x3，跳过日期第一列的空白，星期数多少就跳多少，得出的位置就是第1日的目的位置
;5.利用该月天数x3，得出要搬动多少byte到上面[4]得出的位置28天就搬28x3，31天就搬31x3，来源是 day_str
;6.搬完后，日历字串缓冲完成，开始由印缓冲字串
;7.利用int21 ah=2，或者int 10 ah=13h列印８列文字，每印完一列，游标移下一列。
;8.利用int 16h, ah=0读取使用者输入，RL <- ->
;9,加减年份变数　或　加减月份变数，判定正确后回到代码　s10地址，重新清空缓冲字串
;根据年份变数和月份变数调数搬移的资料和位置，回到方法：1
;10.若输入ESC则离开，回存已经预先读好的原来日期。
;
;----------TajuraTong的理论(完)----------------
;
;注意:这只是一个范例程式。
;若你需要其他功能或加入或删减任何代码，请随便，但不能要求作者为你订制特定的功能或
;根据功课要求而改变这个子程序改变那个子程序，若是代码错误或优化代码的意见，
;则欢迎提出:)
;
;因为没多少空闲，程序只加了一些简单解释，若不明白看一下TajuraTong的理论就是。
;
;(2015/7/1补充)
;关于显示日历的子程序(print_day)，原本是用int10h/ax=1300H的方式
;但不同的win/dos,dos模拟器对这个函数支缓不足，导致显示不出
;现在提供另外两个方法
;1.沿用int10h/ax=1300H
;2.利用int10h/Ax=09　（基本上和方法１等价，只是代码稍复杂）
;3.利用最原始的dos int 29h（黑底白字）
;哪种方法适合，自己取用吧
;
;--------------------------------------------------------
left_key        equ     4bh
right_key       equ     4dh
esc_key         equ     01h
year_header_color equ   4eh
month_header_color equ  3dh
day_color       equ     3fh
back_color      equ     11h
time_att        equ     1dh
title_att       equ     1eH
bottom_att      equ     1aH
first_lineY     equ     3
first_lineX     equ     3
second_lineY    equ     12
second_lineX    equ     26
Line_gap        equ     26
m1y             equ     first_lineY
m1x             equ     first_lineX
m2y             equ     first_lineY
m2x             equ     Line_gap + first_lineX
m3y             equ     first_lineY
m3x             equ     Line_gap + Line_gap + first_lineX
m4y             equ     second_lineY
m4x             equ     first_lineX
m5y             equ     second_lineY
m5x             equ     Line_gap + first_lineX
m6y             equ     second_lineY
m6x             equ     Line_gap + Line_gap + first_lineX
title_x         equ     28
title_y         equ     1
bottom_x        equ     22
bottom_y        equ     22



        DOSSEG
        .MODEL  small
        .data

month           db      37 dup (0)
curret_year     dw      0
curret_month_day dw     0
curret_week     db      0
month_pointer   db      1
year_pointer    dw      2014
half_year_pointer db    0

year_head       db      '2014 / '
month_head      db      'September     '
head_L equ      $ - offset year_head
month_L equ     $ - offset month_head
day_buffer      db      head_L * 7  dup (0)
total_L equ     $ - offset year_head
week_head db   ' S  M  T  W  T  F  S '
Current_last_key db 28

Title_str       db '<<<<   Calendar   >>>>',0
title_L equ     $ - offset title_str
bottom_str      db  1bh,1ah,' to Change Half-year, Esc to Exit'
bottom_L equ    $ - offset bottom_str
show_year       db '2014/05/20',0
show_L equ      $ - offset show_year
time_msg        db  '00:00:00 AM'
time_L equ      $ - offset time_msg

day_str         db ' 1  2  3  4  5  6  7 '
                db ' 8  9 10 11 12 13 14 '
                db '15 16 17 18 19 20 21 '
                db '22 23 24 25 26 27 28 '
                db '29 30 31 '

month_str label byte
        db 'January       ','February      ','March         ','April         '
        db 'May           ','June          ','July          ','August        '
        db 'September     ','October       ','November      ','December      '

att_table       db year_header_color
                db month_header_color
                db day_color
                db day_color
                db day_color
                db day_color
                db day_color
                db day_color

posXY label byte
                db      m1x, m1y
                db      m2x, m2y
                db      m3x, m3y
                db      m4x, m4y
                db      m5x, m5y
                db      m6x, m6y
                position_x      db 0
                position_y      db 0
                month_count     dw 0
                color_line      db 0
                set_flag        db 0

.stack 200h
.code
start:          mov     ax,@data
                mov     ds,ax
                mov     es,ax
                call    cls    	;清屏
                call    clsc    ;draw background 画背景
                mov     cx,3030h ;close cursor 关闭光标
                mov     ah,01h
                int     10h
                call    print_title	;印标题
                mov     ah,2ah ;读现在日期
                int     21h
                mov     curret_year,cx ;存起来
                mov     curret_month_day,dx
                mov     month_count,0
s10:
                mov     set_flag,0
                mov     di,offset year_head ;年份位置
                mov     cx,total_L ;列长度
                mov     al,' ' ;space
                cld
                rep     stosb ;清除旧资料
                mov     ax,year_pointer ;取年份
                mov     di,offset year_head
                xor     dx,dx
                call    print_dec ;印年份
                mov     ax,'/ '
                stosw
                mov     ah,2bh
                mov     dh,month_pointer ;月份指标
                mov     dl,1       ;set first day of month 设定该月第一日
                mov     cx,year_pointer ;年份指标
                int     21h
                mov     ah,2ah  ;读取日期资料
                int     21h
                mov     curret_week,al ;该月第1日是星期几?
                mov     dl,27
s20:
                inc     dl  ;增加日
                mov     ah,2bh ;设定
                mov     dh,month_pointer 
                mov     cx,year_pointer
                int     21h 
                or      al,al ;是否0
                jz      s20 ;若0则成功
                dec     dl ;失败,则dl-1是该月最后一日,这样就可以测到2月或任何一月的月大月小
                mov     Current_last_key,dl ;存
                mov     al,month_pointer ;以下是根据第一日的星期数搬动资料
                dec     al
                cbw
                mov     cx,month_L
                mul     cx
                add     ax,offset month_str
                mov     si,ax
                mov     di,offset month_head
                rep     movsb
                mov     si,offset week_head
                mov     di,offset day_buffer
                mov     cx,head_L
                rep     movsb
                mov     al,curret_week
                mov     ah,al
                add     al,al
                add     al,ah
                cbw
                add     di,ax
                mov     al,Current_last_key
                mov     ah,0
                mov     cx,ax
                add     cx,cx
                add     cx,ax
                mov     si,offset day_str
                rep     movsb
                mov     cx,8		;列8列
                mov     si,month_count   ;以下开始列印8列月份
                shl     si,1
                add     si,offset posXY
                mov     dx,[si]
                mov     bp,offset year_head
                mov     color_line,0   
s40:
                push    cx
                call    print_day	;印一列
                pop     cx
                add     bp,head_L
                inc     color_line
                inc     dh
                loop    s40
                ;
                inc     month_count ;累加计数,6个月
                cmp     month_count,6 ;印完6个月没?
                jae     s44 ;印完
                inc     month_pointer ;下一月份
                jmp     s10 ;再印
s44:
                call    set_current ;restore current year/time ;存回当前月份
                mov     set_flag,1
s45:
                mov     ah,1 ;读键
                int     16h 
                jnz     s46 ;有输入
                call    print_time ;印时钟
                jmp     short s45
s46:
                mov     ah,0
                int     16h
                cmp     ah,esc_key ;ESC键
                jz      quit
                cmp     ah,left_key ;左键
                jnz     s50
                xor     half_year_pointer,1  ;轮换半年
                test    half_year_pointer,1 
                mov     month_pointer,1  ;上半年
                mov     bx,0
                jz      s48
                mov     bx,-1
                mov     month_pointer,7   ;下半年
s48:
                add     year_pointer,bx ;下一年
                jmp     s60
s50:
                cmp     ah,right_key  ;右键
                jnz     s45
                xor     half_year_pointer,1  ;轮换半年
                test    half_year_pointer,1 
                mov     month_pointer,1  
                mov     bx,1
                jz      s52
                mov     bx,0
                mov     month_pointer,7
s52:
                add     year_pointer,bx  ;上半年
s60:
                mov     month_count,0
                jmp     s10			;显示新的半年
quit:
                call    set_current ;回存已经预先读好的原来日期。
                call    cls
                mov     cx,0c0fh  ;open cursor ;开启光标
                mov     ah,01
                int     10h
                mov     ah,4ch  ;离开
                int     21h
;-------------------------------------------------------------
set_current:			;回存原来日期。
                mov     ah,2bh
                mov     dx,curret_month_day
                mov     cx,curret_year
                int     21h
                ret
;-------------------------------------------------------------
;input  :       dx-high ax-low  ;输出数字子程序
;output :       to offset of di

print_dec:	xor	cx,cx
		xchg	bp,dx
		mov	si,10                 ;div by 10
		mov	bx,30h
print_dec1:	or	bp,bp
		jz	print_dec3
		xchg	bp,ax
		xor	dx,dx
		div	si
		xchg	bp,ax
		div	si
		or	dl,bl
		push	dx
		inc	cx
		jmp	short print_dec1

print_dec3:	xor	dx,dx
		div	si
		or	dl,bl
		push	dx
		inc	cx
		or	ax,ax
		jnz	print_dec3

print_dec4:	pop	ax
		stosb
		loop	print_dec4
		ret
;-------------------------------------------------------------
;print_day:	;印日期  (normal machine) :方法１
;                mov     cx,head_L
;                mov     al,color_line
;                mov     bx,offset att_table
;                xlatb
;                mov     bl,al      ;get color attrib
;                mov     bh,0
;                mov     ax,1300h
;                int     10h
;                ret
;--------------------------------------------------------
;print_day:      push    si  (for some machine) 方法3
;
;               mov     bh,0
;                mov     ah,2 ;get new cursor position
;                int     10h
;                mov     cx,head_L
;                mov     si,bp
;
;pday10:         lodsb
;                int     29h
;                loop    pday10
;                pop     si
;                ret
;--------------------------------------------------------
print_day:      push    si  (for some dos emu )方法2

                push    dx
                mov     bh,0
                mov     ah,2 ;get new cursor position
                int     10h
                mov     di,head_L

                mov     si,bp
                mov     al,color_line
                mov     bx,offset att_table
                xlatb
                mov     bl,al
                mov     bh,0

pday10:         lodsb
                mov     cx,1
                mov     ah,9
                int     10h
                mov     bh,0
                inc     dl
                mov     ah,2
                int     10h
                dec     di
                jnz     pday10

                pop     dx
                pop     si
                ret
;--------------------------------------------------------
cls:		mov	ah,0fh                  ;get display mode to al ;清屏
		int	10h
		mov	ah,0                    ;Set display mode
		int	10h
		ret
;--------------------------------------------------------
clsc:           mov     ax,0600h                 ;cls  ;画背景
                mov     bh,back_color            ;attribute
                mov     cx,0                     ;top left
                mov     dl,80
                mov     dh,60
                int     10h
                ret
;---------------------------------------------------------
print_time:     push    es  ;以下是印时钟子程序

                push    ds
                pop     es
                mov     di,offset time_msg

                mov     ah,2ch                 ;get time
                int     21H

                xor     si,si                  ;reset afternoom flag
                mov     al,ch                  ;hours
                cmp     al,12
                jbe     morning
                mov     si,1                   ;set afternoon flag
                sub     al,12
morning:        call    change
                mov     al,cl                  ;mintues
                call    change
                mov     al,dh                  ;seconds
                call    change
                cmp     si,0
                jnz     set_pm
                mov     al,'A'
                jmp     set_am
set_pm:         mov     al,'P'
set_am:         stosb
                mov     cx,time_L
                mov     bl,time_att
                mov     bh,0
                mov     bp,offset time_msg
                mov     dl,68
                mov     dh,0
                mov     ax,1300h
                int     10h
                pop     es
                ret
;---------------------------------------------------------------------------
change:         AAM
                or      ax,3030h
                xchg    ah,al
                stosw
                inc     di
                ret
;---------------------------------------------------------------------------
print_title:    push    es		;以下是印标题和底列的子程序
                push    ds
                pop     es
                mov     cx,title_L
                mov     bl,title_att
                mov     bh,0
                mov     bp,offset Title_str
                mov     dl,title_x
                mov     dh,title_y
                mov     ax,1300h
                int     10h
                ;
                mov     cx,bottom_L
                mov     bl,bottom_att
                mov     bh,0
                mov     bp,offset bottom_str
                mov     dl,bottom_x
                mov     dh,bottom_y
                mov     ax,1300h
                int     10h
                ;
                pop     es
                ret
 ;---------------------------------------------------------------------------
                end     start